JavaScript Promises and Event Loop
Event Loop and Task Queues
JavaScript's concurrency model is based on the "event loop", which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks.
Microtasks vs Macrotasks
- Microtasks: These are tasks that are processed after the currently executing script and before the event loop continues. Examples include promises and
queueMicrotask. - Macrotasks: These include tasks like
setTimeout,setInterval, and I/O tasks.
Microtasks vs Macrotasks Order of Execution
Promise.resolve().then(() => console.log(1));
queueMicrotask(() => console.log(2));
setTimeout(() => console.log(3), 0);
console.log(4);
new Promise(() => console.log(5));
(async () => console.log(6))();
Expected Output:
4
5
6
1
2
3
Explanation:
- Synchronous tasks run first, so
console.log(4)andconsole.log(5)are executed. - Async/Await is essentially syntactic sugar for promises, so
console.log(6)is executed next. - Promises (
.then()) and microtasks (queueMicrotask) are processed in the order they appear. - Macrotasks like
setTimeoutare executed last, even if they have a delay of 0.
Complex Promises and Async/Await Flow
In more complex scenarios involving promises and async/await, understanding the order of execution becomes even more critical.
Async Function with Multiple Promises
async function asyncFunction() {
console.log(1);
new Promise(() => console.log(2));
await new Promise((resolve) =>
setTimeout(() => {
console.log(3);
resolve();
}, 0),
);
console.log(4);
}
new Promise(() => {
console.log(5);
(async () => {
console.log(6);
await asyncFunction();
console.log(7);
})();
console.log(8);
}).then(() => console.log(9));
Expected Output:
5
6
8
1
2
3
4
7
9
Explanation:
console.log(5),console.log(6), andconsole.log(8)are executed synchronously within the first promise.- The async function is called, starting with
console.log(1)andconsole.log(2). - The
awaitinsideasyncFunctiondelays the next synchronous log (console.log(4)) until after thesetTimeoutcallback executes (console.log(3)). - Once all microtasks are finished, the final
.then()is executed, printingconsole.log(9).
Understanding the Execution Order
Combining Promises, Async Functions, and Tasks
(async () => {
const asyncFunc = async () => "asyncFunc";
const promise = new Promise((resolve) => {
console.log("promise");
resolve();
}).then(() => console.log("then"));
console.log("async body");
queueMicrotask(() => console.log("queueMicrotask"));
const results = await Promise.all([asyncFunc(), promise]);
return results;
})();
console.log("script");
Expected Output:
promise
async body
script
queueMicrotask
then
Explanation:
console.log('promise')andconsole.log('async body')are logged synchronously.- The
console.log('script')runs after the synchronous code block. - The
queueMicrotaskruns after all synchronous code but before the.then()callback. - The
console.log('then')is executed last as part of the promise resolution.
Key Takeaways
- Promises are part of the microtask queue, which runs before macrotasks.
- Async/Await pauses function execution until the awaited promise resolves, but microtasks and synchronous code can still execute.
- Understanding the JavaScript event loop is crucial for debugging and writing asynchronous code effectively.